Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core) New tracer API proposal #2430

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Conversation

EmrysMyrddin
Copy link
Collaborator

Description

This PR is a proposal for a new API in the core.

The goal is to provide a reliable way of wrapping each phases of the GraphQL pipeline, including plugin hooks.
The main use case aimed by this new API is Observability (Monitoring and Tracing) of the GraphQL server.

Design choices

  • Only one tracer is allowed. Otherwise, the whole point having a predictable way to wrap the entire phase is defeated, because we will have the same order issue we have with hooks. If a user needs more than one tracer, it's up to it to manually merge tracers as it makes sense for its use case.
  • Tracing wrappers don't have access to phases arguments or results. Existing hooks must be used to access those data. The only goal of the tracer is to wrap the phase in a function.
  • Implied by previous point, tracer can't modify phases arguments or results.
  • The context is passed to all tracing wrappers. It is not advised (yet possible) to modify the context, but to use it as a key of weak map to communicate data with hooks.
  • It is up to the implementor of the tracer to always call the wrapped function, and to handle possible promises in case of execute and subscribe phases.

Example of usage

import otel, type { Context } form "@opentelemetry/core"

function useOTEL(): Plugin {
  const rootSpans = new WeakMap<any, Context>();

  return {
    tracer: {
      execute: ({context}, wrapped) => otel.trace.startActiveSpan(
        'graphql.execute',
        { kind: otel.SpanKind.INTERNAL },
        rootSpans.get(context),  
        wrapped,
      );
    },
    onEnveloped({ context }) {
      const span = otel.trace.startSpan('graphql');
      rootSpans.set(context, otel.trace.setSpan(otel.context.active(), span));
    },
    onExecute: ({ context }) => ({
      onExecuteDone() {
        rootSpans.get(context)?.end();
      },
    }),
  };
}

Type of change

  • New feature (non-breaking change which adds functionality)

Copy link

changeset-bot bot commented Feb 15, 2025

🦋 Changeset detected

Latest commit: 42e2fa8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 38 packages
Name Type
@envelop/core Minor
@envelop/types Minor
@envelop/instruments Major
@envelop/testing Major
@envelop/apollo-datasources Major
@envelop/apollo-federation Major
@envelop/apollo-server-errors Major
@envelop/apollo-tracing Major
@envelop/auth0 Major
@envelop/dataloader Major
@envelop/depth-limit Major
@envelop/disable-introspection Major
@envelop/execute-subscription-event Major
@envelop/extended-validation Major
@envelop/filter-operation-type Major
@envelop/fragment-arguments Major
@envelop/generic-auth Major
@envelop/graphql-jit Major
@envelop/graphql-middleware Major
@envelop/graphql-modules Major
@envelop/immediate-introspection Major
@envelop/live-query Major
@envelop/newrelic Major
@envelop/on-resolve Major
@envelop/opentelemetry Major
@envelop/operation-field-permissions Major
@envelop/parser-cache Major
@envelop/persisted-operations Major
@envelop/preload-assets Major
@envelop/prometheus Major
@envelop/rate-limiter Major
@envelop/resource-limitations Major
@envelop/response-cache-redis Patch
@envelop/response-cache Major
@envelop/sentry Major
@envelop/statsd Major
@envelop/validation-cache Major
@envelop/response-cache-cloudflare-kv Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Feb 15, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@envelop/core 5.2.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/instruments 1.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/apollo-datasources 6.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/apollo-federation 7.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/apollo-server-errors 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/apollo-tracing 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/auth0 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/dataloader 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/depth-limit 6.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/disable-introspection 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/execute-subscription-event 7.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/extended-validation 6.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/filter-operation-type 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/fragment-arguments 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/generic-auth 10.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/graphql-jit 10.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/graphql-middleware 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/graphql-modules 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/immediate-introspection 6.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/live-query 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/newrelic 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/on-resolve 6.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/opentelemetry 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/operation-field-permissions 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/parser-cache 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/persisted-operations 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/preload-assets 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/prometheus 13.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/rate-limiter 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/resource-limitations 7.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/response-cache 8.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/response-cache-cloudflare-kv 4.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/response-cache-redis 4.1.6-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/sentry 14.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/statsd 7.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/validation-cache 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/testing 9.0.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎
@envelop/types 5.2.0-alpha-20250227122402-42e2fa806ec3b7c2936e612924b153a20c8662c3 npm ↗︎ unpkg ↗︎

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 15, 2025

✅ Benchmark Results

     ✓ no_errors
     ✓ expected_result

     checks.............................................: 100.00% ✓ 931982      ✗ 0     
     ✓ { mode:envelop-cache-and-no-internal-tracing }...: 100.00% ✓ 216442      ✗ 0     
     ✓ { mode:envelop-cache-jit }.......................: 100.00% ✓ 371104      ✗ 0     
     ✓ { mode:envelop-just-cache }......................: 100.00% ✓ 214814      ✗ 0     
     ✓ { mode:graphql-js }..............................: 100.00% ✓ 129622      ✗ 0     
     data_received......................................: 3.6 GB  30 MB/s
     data_sent..........................................: 203 MB  1.7 MB/s
     envelop_init.......................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     envelop_total......................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     event_loop_lag.....................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-and-no-internal-tracing }...: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     graphql_context....................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     graphql_execute....................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     graphql_parse......................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     graphql_validate...................................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-cache-jit }.......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:envelop-just-cache }......................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     ✓ { mode:graphql-js }..............................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     http_req_blocked...................................: avg=2.9µs   min=1.18µs   med=2.58µs  max=3.02ms  p(90)=3.45µs  p(95)=3.89µs 
     http_req_connecting................................: avg=32ns    min=0s       med=0s      max=1.91ms  p(90)=0s      p(95)=0s     
     http_req_duration..................................: avg=2.27ms  min=145.54µs med=2.01ms  max=71.53ms p(90)=4.12ms  p(95)=4.68ms 
       { expected_response:true }.......................: avg=2.27ms  min=145.54µs med=2.01ms  max=71.53ms p(90)=4.12ms  p(95)=4.68ms 
     ✓ { mode:envelop-cache-and-no-internal-tracing }...: avg=2.47ms  min=386.38µs med=2.22ms  max=12.88ms p(90)=4.3ms   p(95)=4.68ms 
     ✓ { mode:envelop-cache-jit }.......................: avg=1.29ms  min=145.54µs med=1.06ms  max=14.46ms p(90)=2.09ms  p(95)=2.25ms 
     ✓ { mode:envelop-just-cache }......................: avg=2.49ms  min=298.85µs med=2.23ms  max=32.8ms  p(90)=4.32ms  p(95)=4.71ms 
     ✓ { mode:graphql-js }..............................: avg=4.34ms  min=559.6µs  med=3.75ms  max=71.53ms p(90)=7.27ms  p(95)=7.88ms 
     http_req_failed....................................: 0.00%   ✓ 0           ✗ 465991
     http_req_receiving.................................: avg=33.11µs min=11.1µs   med=28.13µs max=8.68ms  p(90)=44.26µs p(95)=49.35µs
     http_req_sending...................................: avg=12.15µs min=4.09µs   med=9.63µs  max=8.02ms  p(90)=14.36µs p(95)=18.52µs
     http_req_tls_handshaking...........................: avg=0s      min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s     
     http_req_waiting...................................: avg=2.22ms  min=119.45µs med=1.97ms  max=71.46ms p(90)=4.07ms  p(95)=4.62ms 
     http_reqs..........................................: 465991  3883.204372/s
     iteration_duration.................................: avg=2.56ms  min=345.06µs med=2.29ms  max=72.21ms p(90)=4.42ms  p(95)=4.99ms 
     iterations.........................................: 465991  3883.204372/s
     vus................................................: 10      min=10        max=10  
     vus_max............................................: 20      min=20        max=20  

Copy link
Contributor

github-actions bot commented Feb 18, 2025

💻 Website Preview

The latest changes are available as preview in: https://4b34eb9f.envelop.pages.dev

const instruments = composeInstruments(pluginInstruments)

const getEnveloped = envelop({
plugins: [...plugins, { instruments }]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the point of this then is to be able to extract the instruments from an existing list of plugins, and then tack them on the end of the plugin list?

This is an interesting workaround and could work. An alternative approach might be to set a priority number on plugins, so that regardless of the order they are defined in, they are executed based on priority. We could then allocate certain ranges for certain things.

Copy link
Collaborator Author

@EmrysMyrddin EmrysMyrddin Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's closed to the idea yes.

We already have a defined order of plugins execution : It's the order they are defined in the array.
The problem is that, in the case of "wrapping phases", there is often the need to have a different order for "wrapping" and for normal hooks.

So the point of those utils are to extract instruments from plugins (and remove them), so that you can compose them is the order you want. Since you know there is no other instruments, you can put it in the end of the plugin, or at the beginning, it doesn't matter :-)

"typings": "dist/typings/index.d.ts",
"dependencies": {
"@whatwg-node/promise-helpers": "^1.2.1",
"tslib": "^2.5.0"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isnt this usual a dev and peer dependency?

Copy link
Collaborator

@ardatan ardatan Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tslib is imported and used by the JavaScript files compiled by tsc when you have "importHelpers" enabled to dedupe helper methods in the compiled codd

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know to be honest, it is like this in other packages so I just copied the same setup ^^'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants